#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author: bao
# @Date:   2017-01-16 11:59:29
# @Last Modified by:   bao
# @Last Modified time: 2017-05-14 14:14:57

from datetime import datetime
from Unit import *
from Event import *


class Simulator:
    """ Flow-level network simulator"""

    def __init__(self):
        self.eventQueue = EventQueue()
        self.topo = 0
        self.routing = 0
        self.flows = 0
        self.sched = 0

    def AssignTopology(self, topo, cap=1.0 * Gb):
        """
        Network simulator must have an input graph as the topology, isn't it?
        """
        self.topo = topo
        self.topo.SetAllCapacity(cap)

    def AssignRoutingEngine(self, Routing):
        """
        Assign the routing method in a centralized way.
        """
        self.routing = Routing(self.topo)

    def AssignScheduler(self, Scheduler, args):
        """
        Assign the flow scheduler. It also assign the flows to be scheduled.
        Fairness schedule
        """
        self.sched = Scheduler()
        self.sched.AssignFlows(args)
        self.sched.AssignLinks(self.topo.GetLinks())

        self.flows = self.sched.GetAllFlows()
        for flow in self.flows:
            self.routing.BuildPath(flow.startId, flow.endId)
            pathNodeIds = self.routing.GetPath(flow.startId, flow.endId)
            flow.BuildPath(pathNodeIds)
            event = FlowArrivalEvent(flow.startTime, flow)
            self.eventQueue.push(event, event.time)

    def AssignSdnScheduler(self, Scheduler, args):
        """
        Assign the flow scheduler. It also assign the flows to be scheduled.
        Dynamical change the network topology
        """
        self.sched = Scheduler
        self.sched.AssignFlows(args)

        self.flows = self.sched.GetAllFlows()
        for flow in self.flows:
            event = FlowArrivalEvent(flow.startTime, flow)
            self.eventQueue.push(event, event.time)

    def Run(self):
        # Priority event queue, each event may cause new events.
        startTime = datetime.now()
        while len(self.eventQueue.queue) > 0 and \
                (len(self.sched.flows) != len(self.sched.finishedFlows)):

            ev = self.eventQueue.pop()

            if ev.cancelled:
                del ev
                continue

            ev.process_event(self.sched, self.eventQueue)
            del ev

        # Finally, all the flows are finished
        endTime = datetime.now()
        timeDiff = endTime - startTime
        print "Simulator ended. Execution time: %s \n" % timeDiff
